#pragma once
#include "PatchSample.h"

class CCodeBookEntry
{
public:
	CArray<CPatchSample*> Words;
	int Representative;

	CCodeBookEntry()
		: Representative(0)
	{
	}

	virtual ~CCodeBookEntry()
	{
		for(int i = Words.GetCount() - 1; i >= 0; --i)
		{
			CPatchSample* sample = Words.GetAt(i);
			delete sample;
		}
		Words.RemoveAll();
	}


	BOOL IsBelong(CArray<double>& feature, double threshold)
	{
		CArray<double>* representative = &(Words[Representative]->Feature);
		ASSERT(feature.GetCount() == representative->GetCount());

#define ABS(a)	((a) > 0 ? (a) : -(a))

		// we use Mahattan distance
		// SHOULD parameterize this, but only when we have more time... T_T
		double dDistance = 0, x, y;
		for(INT_PTR i = feature.GetCount() - 1; i >= 0; --i)
		{
			x = feature[i];
			y = representative->GetAt(i);
			dDistance += ABS(x - y) / (ABS(x) + ABS(y) + 1);
		}
		//dDistance /= feature.GetCount();

#undef ABS

		return dDistance <= threshold;
	}

	void FindRepresentative()
	{
		if(Words.GetCount() <= 0)
			return;

		CArray<double> representative;
		representative.SetSize(Words[0]->Feature.GetCount());
		for(int i = representative.GetCount() - 1; i >= 0; representative.SetAt(i--, 0));

		INT_PTR iCount = Words.GetCount();
		for(INT_PTR i = 0; i < iCount ; ++i)
		{
			CPatchSample* sample = Words[i];
			ASSERT(sample->Feature.GetCount() == representative.GetCount());

			for(int j = sample->Feature.GetCount() - 1; j >= 0; --j)
			{
				representative[j] = (representative[j] * (iCount - 1) + sample->Feature[j]) / (double)iCount;
			}
		}
		Representative = Words.Add(new CPatchSample(CString(L""), representative));
	}

	void Serialize(CMarkup& xml)
	{
		xml.AddElem(L"CCodeBookEntry");

		xml.IntoElem();

		CString sRep;
		sRep.Format(L"%d", Representative);
		xml.AddElem(L"Representative", sRep);
		int iCount = Words.GetCount();
		for(int i = 0; i < iCount; ++i)
		{
			Words[i]->Serialize(xml);
		}

		xml.OutOfElem();
	}

	static CCodeBookEntry* Deserialize(CMarkup& xml)
	{
		CCodeBookEntry* entryRet = new CCodeBookEntry();
		CString sRep;
		xml.IntoElem();
		if(xml.FindChildElem(L"Representative"))		sRep = xml.GetChildData();
		while(xml.FindChildElem(L"PatchSample"))
		{
			CPatchSample* sample = CPatchSample::Deserialize(xml);
			if(sample)
				entryRet->Words.Add(sample);
		}
		entryRet->Representative = _wtoi(sRep);
		xml.OutOfElem();

		return entryRet;
	}

};

class CCodeBook
{
private:
	double m_dClusterThreshold;

public:
	CArray<CCodeBookEntry*> Entries;

	virtual ~CCodeBook()
	{
		for(int i = Entries.GetCount() - 1; i >= 0; --i)
		{
			CCodeBookEntry* entry = Entries.GetAt(i);
			delete entry;
		}
		Entries.RemoveAll();
	}

	void SetThreshold(double dThreshold)
	{
		m_dClusterThreshold = dThreshold;
	}

	INT_PTR FindCluster(CArray<double>& feature)
	{
		for(INT_PTR i = Entries.GetCount() - 1; i >= 0; --i)
		{
			if(Entries[i]->IsBelong(feature, m_dClusterThreshold))
				return i;
		}
		return -1;
	}

	void FindReprestantives()
	{
		for(INT_PTR i = Entries.GetCount() - 1; i >= 0; --i)
		{
			Entries[i]->FindRepresentative();
		}
	}

	void Serialize(CMarkup& xml)
	{
		xml.AddElem(L"CodeBook");
		xml.IntoElem();
		int iEntryCnt = Entries.GetCount();
		for(int i = 0; i < iEntryCnt; ++i)
		{
			Entries[i]->Serialize(xml);
		}
		xml.OutOfElem();
	}

	static CCodeBook* Deserialize(CMarkup& xml)
	{
		if(xml.FindElem(L"CodeBook"))
		{
			CCodeBook* codeBookRet = new CCodeBook();
			while(xml.FindChildElem(L"CCodeBookEntry"))
			{
				CCodeBookEntry* entry = CCodeBookEntry::Deserialize(xml);
				if(entry)
					codeBookRet->Entries.Add(entry);
			}
			return codeBookRet;
		}
		return NULL;
	}
};